package server.core;

import server.*;
import server.deploy.FilterMap;
import server.valves.ValveBase;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/*
StandWrapper容器的基础阀,该阀的invoke方法会获取wrapper对应的servlet实例,构建过滤器链并调用过滤器的doFilter方法和servlet的service方法
/*
2023/3/1 该类内容全部写完,后续直接使用,无需修改
by cyl
 */
final class StandardWrapperValve extends ValveBase {
    private static final String info = "server.core.StandardWrapperValve/1.0";
    //方法----------------------------------------------------------------
    private void log(String message) {
        Logger logger = null;
        if (container != null)
            logger = container.getLogger();
        if (logger != null) {
            logger.log("StandardWrapperValve[" + container.getName() + "]: " + message);
        } else {
            String containerName = null;
            if (container != null)
                containerName = container.getName();
            System.out.println("StandardWrapperValve[" + containerName + "]: " + message);
        }
    }

    private void log(String message, Throwable throwable) {
        Logger logger = null;
        if (container != null)
            logger = container.getLogger();
        if (logger != null) {
            logger.log("StandardWrapperValve[" + container.getName() + "]: " + message, throwable);
        } else {
            String containerName = null;
            if (container != null)
                containerName = container.getName();
            System.out.println("StandardWrapperValve[" + containerName + "]: " + message);
            System.out.println("" + throwable);
            throwable.printStackTrace(System.out);
        }
    }
    //实现Valve接口----------------------------------------------------------------
    @Override
    public String getInfo() {
        return info;
    }


    /*
    首先检查servlet是否可用,不可用返回503状态码
    可用时获取servelet实例,并创建过滤器链将能拦截到该请求的过滤器添加到链中
    调用过滤器链的doFilter方法依次执行所有过滤器的doFilter方法,最后执行servlet的service方法
    如果执行过程中出现异常,说明服务器内部代码错误,返回500状态码
    如果servlet一直不可用,卸载servlet节约资源
     */
    @Override
    public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
        //Servlet是否不可用
        boolean unavailable = false;
        Throwable throwable = null;
        StandardWrapper wrapper = (StandardWrapper)getContainer();
        HttpServletResponse hres = (HttpServletResponse)response.getResponse();
        //本应用不可用,响应返回503
        if (!((Context)wrapper.getParent()).getAvailable()) {
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "This application is not currently available");
            unavailable = true;
        }
        //wrapper不可用
        if (!unavailable && wrapper.isUnavailable()) {
            log("Servlet {" + wrapper.getName() + "} is currently unavailable");
            long available = wrapper.getAvailable();
            if (available > 0 && available < Long.MAX_VALUE)
                //告诉客户端时间戳走到哪里时该Servlet变为可用
                hres.setDateHeader("Retry-After", available);
            //响应返回503
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "当前Servlet不可用");
            unavailable = true;
        }
        //获取servlet实例
        Servlet servlet = null;
        if (!unavailable) {
            try {
                //获取servlet实例对象
                servlet = wrapper.allocate();
            } catch (Throwable t) {
                log("Allocate exception for servlet {" + wrapper.getName() + "}", t);
                throwable = t;
                exception(request, response, t);
                servlet = null;
            }
        }
        ApplicationFilterChain filterChain = createFilterChain(request, servlet);
        try {
            if (servlet != null && filterChain != null) {
                filterChain.doFilter(request.getRequest(), response.getResponse());
            }
        } catch (IOException e) {
            log("Servlet.service() for servlet {" + wrapper.getName() + "} threw exception", e);
            throwable = e;
            exception(request, response, e);
        } catch (UnavailableException e) {
            log("Servlet.service() for servlet {" + wrapper.getName() + "} threw exception", e);
            wrapper.unavailable(e);
            long available = wrapper.getAvailable();
            if ((available > 0L) && (available < Long.MAX_VALUE))
                hres.setDateHeader("Retry-After", available);
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Servlet {" + wrapper.getName() + "} is currently unavailable");
        } catch (Throwable e) {
            log("Servlet.service() for servlet {" + wrapper.getName() + "} threw exception", e);
            throwable = e;
            exception(request, response, e);
        }

        try {
            //servlet不可用,卸载
            if (servlet != null && wrapper.getAvailable() == Long.MAX_VALUE) {
                wrapper.unload();
            }
        } catch (Throwable e) {
            log("Servlet {" + wrapper.getName() + "} thrw unload() exception", e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }
    }

    //将异常对象放在请求request的属性表中,并设置响应状态码为500
    private void exception(Request request, Response response, Throwable throwable) {
        ServletRequest servletRequest = request.getRequest();
        servletRequest.setAttribute("javax.servlet.error.exception", throwable);
        HttpServletResponse hsr = (HttpServletResponse)response.getResponse();
        hsr.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }

    //根据servlet创建过滤器链,再根据请求路径和servlet名将需要执行拦截的过滤器添加到过滤器链中,最后返回链
    private ApplicationFilterChain createFilterChain(Request request, Servlet servlet) {
        if (servlet == null)
            return null;
        //创建过滤器链
        ApplicationFilterChain filterChain = new ApplicationFilterChain();
        filterChain.setServlet(servlet);

        StandardWrapper wrapper = (StandardWrapper) getContainer();
        StandardContext context = (StandardContext)wrapper.getParent();

        //获取全部过滤器拦截信息
        FilterMap[] filterMaps = context.findFilterMaps();
        if (filterMaps == null || filterMaps.length == 0)
            return filterChain;

        HttpServletRequest hreq = (HttpServletRequest)request.getRequest();
        //应用程序路径(/应用名)
        String contextPath = hreq.getContextPath();
        if (contextPath == null)
            contextPath = "";
        //uri路径(/应用名/目录1/目录2/目录3...)
        String requestURI = ((HttpRequest) request).getDecodedRequestURI();
        //去掉应用名后的请求uri路径(匹配过滤器拦截路径)
        String requestPath = null;
        if (requestURI.length() >= contextPath.length())
            requestPath = requestURI.substring(contextPath.length());

        //servlet名
        String servletName = wrapper.getName();
        for (FilterMap filterMap : filterMaps) {
            //过滤器拦截不到该路径,且不拦截该Servlet名,换下一个过滤器
            if (!matchFiltersURL(filterMap, requestPath) && !matchFiltersServlet(filterMap, servletName))
                continue;
            //能拦截到,从应用中拿到FilterConfig
            ApplicationFilterConfig filterConfig =
                    (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null)
                continue;
            //添加到过滤器链中
            filterChain.addFilter(filterConfig);
        }
        return filterChain;
    }

    /*
     * 拦截路径配置：
     * 具体资源路径： /index.jsp 只有访问index.jsp资源时，过滤器才会被执行
     * 拦截目录： /user/* 访问/user下的所有资源时，过滤器都会被执行
     * 后缀名拦截： *.jsp 访问所有后缀名为jsp资源时，过滤器都会被执行
     * 拦截所有资源：/* 访问所有资源时，过滤器都会被执行
     *
     * 如果给的requestPath能被拦截到时,返回true,否则返回false
     */
    private boolean matchFiltersURL(FilterMap filterMap, String requestPath) {

        if (requestPath == null)
            return false;
        //拦截路径
        String patternPath = filterMap.getUrlPattern();
        if (patternPath == null)
            return false;
        //请求的路径与要拦截的路径相同,需要拦截
        if (requestPath.equals(patternPath))
            return true;
        //拦截器要拦截所有路径
        if ("/*".equals(patternPath))
            return true;
        //拦截器要拦截的路径是/xx/xxx/*形式
        if (patternPath.endsWith("/*")) {
            String comparePath = requestPath;
            //从当前路径循环开始每次去掉最后的/及后面的内容
            //得到的新路径加上/*与拦截路径相同时拦截
            while (true) {
                if (patternPath.equals(comparePath + "/*"))
                    return true;
                int index = comparePath.lastIndexOf('/');
                if (index < 0)
                    break;
                comparePath = comparePath.substring(0, index);
            }
            //去除完所有的/也不匹配,匹配失败
            return false;
        }
         //拦截后缀名,如*.jsp 访问所有后缀名为jsp资源时，过滤器都会被执行
        if (patternPath.startsWith("*.")) {
            int dropIndex = requestPath.lastIndexOf('.');
            int slashIndex = requestPath.lastIndexOf('/');
            if (slashIndex >= 0 && dropIndex > slashIndex) {
                return patternPath.equals("*" + requestPath.substring(dropIndex));
            }
        }
        return false;
    }

    //如果拦截器要拦截类名为servletName的Servlet类,返回true
    //否则返回false
    private boolean matchFiltersServlet(FilterMap filterMap, String servletName) {
        if (servletName == null)
            return false;
        return servletName.equals(filterMap.getServletName());
    }
}
